#include "deluacore.h"

#include <QDebug>
#include <QDir>
#include <QTextCodec>

#include "xxtea.h"

void DeluacCore::startDecrypt(const DeluacArgs &args)
{
    this->args = args;
    this->args.isDecrypt = true;
    QThread::start();
}

void DeluacCore::startGuess(const DeluacArgs &args)
{
    this->args = args;
    this->args.isDecrypt = false;
    QThread::start();
}

void DeluacCore::run()
{
    QStringList filePaths;
    fetchFiles(args.searchPath, QString("*.%1").arg(args.srcExtension), filePaths);
    if (args.isDecrypt)
    {
        runDecrypt(filePaths);
    }
    else
    {
        runGuess(filePaths);
    }
}

void DeluacCore::fetchFiles(const QString &searchFolder, const QString &nameFilter, QStringList &targetList)
{
    QDir fileEntry(searchFolder), folderEntry(searchFolder);
    fileEntry.setNameFilters(QStringList() << nameFilter);
    QStringList filePaths = fileEntry.entryList(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
    QStringList folderPaths = folderEntry.entryList(QDir::Dirs | QDir::NoDotAndDotDot);

    for (const auto &folderPath : folderPaths)
    {
        fetchFiles(QString("%1%2%3").arg(searchFolder).arg(QDir::separator()).arg(folderPath), nameFilter, targetList);
    }
    for (const auto &filePath : filePaths)
    {
        targetList.append(QString("%1%2%3").arg(searchFolder).arg(QDir::separator()).arg(filePath));
    }
}

QByteArray DeluacCore::getXxteaSign(const QString &filePath)
{
    QByteArray byteText;
    QFile fileEntry(filePath);
    if (fileEntry.open(QIODevice::ReadOnly))
    {
        QByteArray content = fileEntry.readAll();
        QTextCodec::ConverterState state;
        QTextCodec *codec = QTextCodec::codecForName("UTF-8");
        codec->toUnicode(content, content.size(), &state);
        if (state.invalidChars > 0)
        {
            for (int i = 0; i < content.size(); i++)
            {
                if (content.at(i) <= 0)
                {
                    byteText = content.left(i);
                    break;
                }
            }
            qDebug() << filePath.toStdString().c_str() << ":" <<state.invalidChars;
        }
    }
    return byteText;
}

QByteArray DeluacCore::getXxteaKey(const QString &filePath)
{
    QByteArray byteText;
    if (!args.xxteaSign.isEmpty())
    {
        QFile fileEntry(filePath);
        if (fileEntry.open(QIODevice::ReadOnly))
        {
            QByteArray content = fileEntry.readAll();
            int from = 0;
            while ((from = content.indexOf(args.xxteaSign, from)) != -1)
            {
                int last = from - 1;
                while (last > 0 && content.at(last) == 0)
                    last--;
                if (last < from - 1)
                {
                    for (int i = last; i > 0; i--)
                    {
                        if (content.at(i) <= 0)
                        {
                            byteText = content.mid(i + 1, last - i);
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }
    return byteText;
}

void DeluacCore::runDecrypt(const QStringList &filePaths)
{
    int count = 0;
    int total = filePaths.size();
    for (const auto &filePath : filePaths)
    {
        QFile fileReader(filePath);
        int progressValue = (++count) * 100 / total;
        if (fileReader.open(QIODevice::ReadOnly))
        {
            xxtea_long plainLen = 0;
            QByteArray readContent = fileReader.readAll();
            fileReader.close();
            if (readContent.size() > args.xxteaSign.length())
            {
                char *usefulData = readContent.data() + args.xxteaSign.length();
                int usefulLen = readContent.size() - args.xxteaSign.length();
                uint8_t *plainData = xxtea_decrypt(
                    reinterpret_cast<uint8_t *>(usefulData),
                    static_cast<xxtea_long>(usefulLen),
                    reinterpret_cast<uint8_t *>(args.xxteaKey.toLatin1().data()),
                    static_cast<xxtea_long>(args.xxteaKey.toLatin1().length()),
                    &plainLen);

                if (plainData != nullptr && plainLen > 0)
                {
                    QFileInfo fileInfo(filePath);
                    QFile fileWritter(QString("%1%2%3.%4").arg(fileInfo.dir().path()).arg(QDir::separator())
                                      .arg(fileInfo.completeBaseName()).arg(args.destExtension));
                    if (fileWritter.open(QIODevice::WriteOnly))
                    {
                        QByteArray plainContent(reinterpret_cast<char*>(plainData), static_cast<int>(plainLen));
                        fileWritter.write(plainContent);
                        fileWritter.close();
                        decryptCompleted(filePath, DeluacState::Succeed, progressValue);
                        continue;
                    }
                }
            }
            QTextCodec::ConverterState state;
            QTextCodec *codec = QTextCodec::codecForName("UTF-8");
            codec->toUnicode(readContent, readContent.size(), &state);
            if (state.invalidChars == 0)
            {
                decryptCompleted(filePath, DeluacState::Original, progressValue);
                continue;
            }
        }
        decryptCompleted(filePath, DeluacState::Failed, progressValue);
    }
}

void DeluacCore::runGuess(const QStringList &filePaths)
{
    // 首先猜测签名再猜测密钥
    QByteArray byteText1;
    QByteArray byteText2;
    QByteArray byteText3;
    for (const auto &filePath : filePaths)
    {
        QByteArray byteText = getXxteaSign(filePath);
        if (byteText1.isEmpty()) byteText1 = byteText;
        else if (byteText2.isEmpty()) byteText2 = byteText;
        else if (byteText3.isEmpty()) byteText3 = byteText;
        if (!byteText3.isEmpty()) break; // 最后一次猜测完毕退出
    }
    args.xxteaSign = "⚠未检测到XXTEA签名";
    args.xxteaKey = "⚠未检测到XXTEA密钥";
    if (byteText1.isEmpty() || byteText2.isEmpty() || byteText3.isEmpty())
    {
        guessCompleted(args.xxteaSign, args.xxteaKey);
        return;
    }
    // 取三个中长度最小的文本
    int length = byteText1.length();
    if (byteText2.length() < length)
    {
        length = byteText2.length();
    }
    if (byteText3.length() < length)
    {
        length = byteText3.length();
    }
    int i = 0;
    for (; i < length; i++)
    {
        if (byteText1.at(i) != byteText2.at(i) || byteText2.at(i) != byteText3.at(i))
        {
            args.xxteaSign = byteText2.left(i);
            break;
        }
    }
    if (i == length)
    {
        args.xxteaSign = byteText3.left(length);
    }
    if (args.xxteaSign.isEmpty())
    {
        guessCompleted(args.xxteaSign, args.xxteaKey);
    }
    else
    {
        QStringList filePaths;
        fetchFiles(QString("%1%2%3").arg(args.searchPath).arg(QDir::separator()).arg("lib"),
                   QString("*.%1").arg("so"), filePaths);
        for (const auto &filePath : filePaths)
        {
            QByteArray byteText = getXxteaKey(filePath);
            if (!byteText.isEmpty())
            {
                args.xxteaKey = byteText;
                guessCompleted(args.xxteaSign, args.xxteaKey);
                return;
            }
        }
        guessCompleted(args.xxteaSign, args.xxteaKey);
    }
}
